/* uDig - User Friendly Desktop Internet GIS client
 * http://udig.refractions.net
 * (C) 2004-2008, Refractions Research Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package symbology;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Stack;

import org.xml.sax.*;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;

import com.sun.org.apache.xerces.internal.parsers.SAXParser;

/**
 * Implements "post-order" parsing of the XML tree, working from the bottom to the top.
 * <p>
 * Each child element can produce a single object. When all the children are processed, the list of 
 * child objects is handed to the parent. The parent in turn can produce a single object, which
 * is passed to its parent.
 * </p>
 * The abstract method "processElement" processes each element in the xml parse tree,
 * accepting an element name, attributes, text and child objects.
 * Subclasses of ParseXml provide their own "processElement" to perform application specific
 * processing.
 * </p>
 * @author John Morris
 * @since 1.1.0
 */
public abstract class ParseXml extends DefaultHandler {
    
    
    // Keep a stack of XML tags so we can process them when all the data is collected
    class Result {
        private String tag;
        public String characters;
        public Attributes attributes;
        public ArrayList<Object> objects;
        
        Result(String tag, Attributes attributes) {
            this.tag = tag;
            // Note: make a persistent copy of the attributes
            //  because SAX reuses the object with every element
            this.attributes = new AttributesImpl(attributes);
            this.characters = "";
            this.objects = new ArrayList<Object>();
        }
    }
    
        
    Stack<Result> stack;
    
    
    /**
     * parses the XML file
     * @param fileName is the name of the xml file
     * @return object built up from the xml parse tree
     */
    public Object parse(String fileName) throws SAXException, IOException {
        
        // Create a stack with a top entry to hold the final result
        stack = new Stack<Result>();
        stack.push(new Result("$$TOP$$", new AttributesImpl()));
        
        // Create a parser to parse the XML
        SAXParser p = new SAXParser();
        p.setContentHandler(this);
        
        // Parse the file, catching errors
        try {
            p.parse(fileName);
        }
        catch (SAXException e) {
            throw e;
        }
        catch (IOException e) {
            throw e;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        
        // return the final object (or null)
        ArrayList<Object> objects = stack.peek().objects;
        if (objects.isEmpty())
            return null;
        else
            return objects.get(0);
    }
            
    
    /**
     * is SAX procedure to process XML elements as they are first encountered. It saves attributes on stack.
     */
    public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) {
        
        // Create a new stack frame for the current element
        stack.push(new Result(qName, attributes));
    }
    
    /**
     * is SAX procedure to process character text in body of element. Saves it on stack.
     */
    public void characters(char[] ch, int start, int length) {
        
        // Append the characters to the ones we've already accumulated
        stack.peek().characters += new String(ch, start, length);
    }
            
    /**
     * is SAX procedure to process end of element. Invokes "processElement" which does real work.
     */
    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
        
        // Get the current information off the stack
         Result r = stack.pop();
                
         // Invoke the custom processor to decide what to do with it
         Object o = processElement(r.tag, r.attributes, r.characters, r.objects);
         
         // If an object was returned, then add it to the objects in our caller's frame
         if (o != null)
             stack.peek().objects.add(o);
    }

    
    /**
     * getDouble gets a numeric value from an XML attribute list
     * @param attributes - the XML attribute list
     * @param name - the name of the attribute 
     * @param def - the default value
     * @return - the numeric value
     * @throws SAXException if there is a parsing error
     */
    public static double getDouble(Attributes attributes, String name, double def) throws SAXException {
        String s = attributes.getValue(name);
        if (s == null) return def;
        
        double d;
        try {
            d = Double.parseDouble(s);
        }
        catch (Exception e) {
            throw new SAXException("Unable to parse number '" + s + "'");
        }
        
        return d;
    }
    
    public static String getString(Attributes attributes, String name, String def) {
        String s = attributes.getValue(name);
        if (s == null) return def;
        else           return s;
    }
    
    // This is the main customizable element of this class.
    //   When an element is complete, process the tag info, characters, and nested objects to produce an object
    public abstract Object processElement(String tag, Attributes attributes, 
                   String characters, ArrayList<Object> objects) throws SAXException;
    
}
